﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Xml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.TypeConverters;

namespace Microsoft.WowAddonStudio.FrameXml.Serialization
{
    /// <summary>
    /// Represents an RGBA (red, green, blue, alpha) color.
    /// </summary>
    [Serializable]
    [DesignerCategory("code")]
    [XmlType(Namespace = "http://www.blizzard.com/wow/ui/")]
    [DebuggerDisplay("Red = {Red}, Green = {Green}, Blue = {Blue}, Alpha = {Alpha}")]
    [TypeConverter(typeof(ColorTypeConverter))]
    public class Color : SerializationObject, ICopiable<Color>, ICloneable, IEquatable<Color>, IPropertyValidator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Color"/> class.
        /// </summary>
        public Color()
        {
            this.InitializeDefaults();

        }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Color"/> class.
        /// </summary>
        /// <param name="alpha">The alpha.</param>
        /// <param name="blue">The blue.</param>
        /// <param name="green">The green.</param>
        /// <param name="red">The red.</param>
        internal Color(float alpha, float red, float green, float blue)
        {
            Alpha = alpha;
            Blue = blue;
            Green = green;
            Red = red;
        }

        /// <summary>
        /// Gets or sets the red component.
        /// </summary>
        [XmlAttribute("r")]
        [NotifyParentProperty(true)]
        [Description("The Red component of color.")]
        public float Red { get; set; }

        /// <summary>
        /// Gets or sets the green component.
        /// </summary>
        [XmlAttribute("g")]
        [NotifyParentProperty(true)]
        [Description("The Green component of color.")]
        public float Green { get; set; }

        /// <summary>
        /// Gets or sets the blue component.
        /// </summary>
        [XmlAttribute("b")]
        [NotifyParentProperty(true)]
        [Description("The Blue component of color.")]
        public float Blue { get; set; }

        /// <summary>
        /// Gets or sets the alpha level.
        /// </summary>
        [XmlAttribute("a")]
        [DefaultValue(typeof (float), "1")]
        [NotifyParentProperty(true)]
        [Description("The Alpha component of color.")]
        public float Alpha { get; set; }

        /// <summary>
        /// Converts the color to a <see cref="System.Drawing.Color"/>.
        /// </summary>
        /// <returns>A <see cref="System.Drawing.Color"/> structure.</returns>
        public System.Drawing.Color ToColor()
        {
            return System.Drawing.Color.FromArgb((int)Math.Ceiling((Alpha * 255)),
                (int)Math.Ceiling((Red * 255)), (int)Math.Ceiling((Green * 255)), (int)Math.Ceiling((Blue * 255)));
        }

        /// <summary>
        /// Converts the <see cref="System.Drawing.Color"/> to a Color.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <returns></returns>
        public static Color FromColor(System.Drawing.Color color)
        {
            return new Color(Round(color.A / 255.0f, 2), Round(color.R / 255.0f, 2), Round(color.G / 255.0f, 2), Round(color.B / 255.0f, 2));
        }

        /// <summary>
        /// Rounds a floating-point value to the specified precision.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="digits">The digits.</param>
        /// <returns></returns>
        private static Single Round(Single value, int digits)
        {
            return (Single)Math.Round(value, digits);
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>The cloned instance.</returns>
        public object Clone()
        {
            return this.Copy();
        }

        /// <summary>
        /// Creates a new object that is a copy of the instance.
        /// </summary>
        /// <returns>The copied instance.</returns>
        /// <remarks>Performs a deep copy.</remarks>
        public Color Copy()
        {
            return new Color
                        {
                            Alpha = Alpha,
                            Blue = Blue,
                            Green = Green,
                            Red = Red
                        };
        }

        /// <summary>
        /// Equalses the specified obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public bool Equals(Color obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            return obj.Red == Red && obj.Green == Green && obj.Blue == Blue && obj.Alpha == Alpha;
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != typeof (Color))
                return false;
            return Equals((Color) obj);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                int result = Red.GetHashCode();
                result = (result*397) ^ Green.GetHashCode();
                result = (result*397) ^ Blue.GetHashCode();
                result = (result*397) ^ Alpha.GetHashCode();
                return result;
            }
        }

        /// <summary>
        /// Determines whether the specified property name is valid.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="value">The value.</param>
        /// <returns>
        /// 	<c>true</c> if the specified property name is valid; otherwise, <c>false</c>.
        /// </returns>
        public bool ValidatePoperty(string propertyName, object value)
        {
            if(propertyName == "Alpha" || propertyName == "Red" || propertyName == "Blue" || propertyName == "Green")
            {
                float floatValue = (float) value;

                if(floatValue < 0 || floatValue > 1)
                    throw new ArgumentException(String.Format("{0} property must be between 0 and 1.", propertyName));
            }
            else
            {
                throw new NotImplementedException(String.Format("{0} property validation is not supported.", propertyName));
            }

            return true;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return string.Format("Color: [Red = {0}, Green = {1}, Blue = {2}, Alpha = {3}]", Red, Green, Blue, Alpha);
        }
    }
}